﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using System.Net;
using System.IO;
using System.Xml;

namespace DailyRssDownload.Lib {
	public abstract class Entry {
		public string Title { get; set; }
		public DateTime Published { get; set; }
		public string Url { get; set; }
		public string Description { get; set; }
		public string OsSafeTitle { get; set; }
		public Feed Feed { get; set; }
		public string Summary { get; set; }
		public string FullText { get; set; }
		public int Index { get; set; }
		public string Content { get; set; }

		private const string cstrDefaultContentStartMarker = @"\<body[^\>]*\>";
		private const string cstrDefaultContentEndMarker = @"\<\/body\>";

		private static readonly XNamespace _xhtmlNamespace = "http://www.w3.org/1999/xhtml";

		public Entry() { }

		protected Entry(XElement item, Feed objFeed) {
			this.Feed = objFeed;
			Index = item.ElementsBeforeSelf().Count();
		}

		public static string MakeOsSafeName(string str) {
			// remove: '\', '/', ':', '*', '?', '"', '<', '>', '|', '.', ' ', '''
			return Regex.Replace(str, @"[\\\\\\/\\:\\*\\?\\""\\<\\>\\|\\,\. \']", "");
		}

		internal static string _strDownloadDir;
		internal static string _strSubdirectory;

		public static string DownloadImagesAndReplaceSrc(string strEntryHtml, string strDownloadToDirectory, string strSubdirectory) {
			_strDownloadDir = Path.Combine(strDownloadToDirectory, strSubdirectory);
			_strSubdirectory = strSubdirectory;

			MatchEvaluator objGetImages = new MatchEvaluator(DownloadImage);

			return Regex.Replace(strEntryHtml, @"\<img [^\>]*\>", objGetImages);
		}

		public static string DownloadImage(Match objMatch) {
			string strImgElement = objMatch.Value;
			string strUrl = GetUrlFromImageElement(strImgElement);
			string strImageName = GetImageNameFromImageUrl(strUrl);

			WebClient client = new WebClient();

			try {
				if (!Directory.Exists(_strDownloadDir))
					Directory.CreateDirectory(_strDownloadDir);
				
				string strDownloadImageLocation = Path.Combine(_strDownloadDir, strImageName);
				client.DownloadFile(strUrl, strDownloadImageLocation);
				return strImgElement.Replace(strUrl, _strSubdirectory + "/" + strImageName);
			} catch (WebException ex) {
				if (ex.InnerException != null) {
					if (ex.InnerException.Message == "Illegal characters in path.")
						return String.Format("[Unable To Download Image: {0}]", strUrl);
				} 

				Log.LogError(ex);
				return String.Format("[Unable To Download Image: {0}]", strUrl);
			} catch (Exception ex) {
				Log.LogError(ex);
				return String.Format("[Unable To Download Image: {0}]", strUrl);
			}
		}

		public static string GetUrlFromImageElement(string strImgElement) {
			return Regex.Match(strImgElement, "src=[\"']([^\"]*)[\"']").Groups[1].Value;
		}

		public static string GetImageNameFromImageUrl(string strUrl) {
			int intLastSlash = strUrl.LastIndexOf('/');
			return strUrl.Substring(intLastSlash + 1);
		}

		public static string SurroundWithBasicHtml(
			string strContent, 
			string strTitle, 
			DateTime dtPublished, 
			string strUrl,
			string strPreviousUrl,
			string strNextUrl
			) {

			StringBuilder sb = new StringBuilder(300);

			// <html><head><title>
			sb.AppendFormat("<html><head><title>{0}</title><link type=\"text/css\" rel=\"Stylesheet\" href=\"../ContentStyle.css\" /><meta name=\"keywords\" type=\"overview\"/>", strTitle);
			
			// <link> next & previous
			if (!String.IsNullOrEmpty(strPreviousUrl))
				sb.AppendFormat("<link rel='prev' href='{0}'/>", strPreviousUrl);
			if (!String.IsNullOrEmpty(strNextUrl))
				sb.AppendFormat("<link rel='next' href='{0}'/>", strNextUrl);
			
			// <body>
			sb.Append("<body>");
			
			// <a href="next">
			if (!String.IsNullOrEmpty(strPreviousUrl))
				sb.AppendFormat("<a id='prevPage' href='{0}'>Prev</a> ", strPreviousUrl);
			if (!String.IsNullOrEmpty(strNextUrl))
				sb.AppendFormat("<a id='nextPage' href='{0}'>Next</a>", strNextUrl);

			// <h1>title</h1>
			sb.AppendFormat("<h1>{0}</h1>", strTitle);

			// Published
			sb.AppendFormat("Published: {0}<br /><br />", dtPublished);
			
			// Content
			sb.Append(strContent);
			
			// </body></html>
			sb.Append("</body></html>");

			return sb.ToString();
		}

		/// <summary>
		/// Downloads Images, Writes the Content File, and Gets the Summary
		/// </summary>
		/// <param name="strDownloadToDirectory"></param>
		public void Download(string strDownloadToDirectory) {
			if (!Directory.Exists(strDownloadToDirectory))
				Directory.CreateDirectory(strDownloadToDirectory);

			string strRssDescription = DownloadImagesAndReplaceSrc(this.Description, strDownloadToDirectory, this.OsSafeTitle);

			if (Feed.SummarySource == SummarySource.SummarySourcesEnum.EntireDescription) {
				this.Summary = strRssDescription;
			}

			if (Feed.ContentSource == ContentSource.ContentSourcesEnum.Summary) {
				this.Content = strRssDescription;
			}

			if (Feed.ContentSource == ContentSource.ContentSourcesEnum.Download) {
				string strResponse = GetHtmlFromUrl(this.Url);
				strResponse = DownloadImagesAndReplaceSrc(strResponse, strDownloadToDirectory, this.OsSafeTitle);
				strResponse = ExtractContentFromHtml(strResponse, this.Feed.ContentStartMarker, this.Feed.ContentEndMarker);
				this.Content = strResponse;
			}

			if (Feed.SummarySource == SummarySource.SummarySourcesEnum.BeginningOfDescription) {
				this.Summary = ExtractSummaryFromContent(this.Content, 300);
			}

		}

		private static string ExtractSummaryFromContent(string strHtmlContent, int intChars) {
			// remove all tags
			string strNonHtmlContent = Regex.Replace(strHtmlContent, @"\<[^\>]*\>", " ");
			// consolidate whitespace
			strNonHtmlContent = Regex.Replace(strNonHtmlContent, @"\s+", " ");
			// get the first X characters and trim
			int intCharsToGet = Math.Min(intChars, strNonHtmlContent.Length);
			strNonHtmlContent = strNonHtmlContent.Substring(0, intCharsToGet).Trim();
			return strNonHtmlContent;
		}

		private static string ExtractContentFromHtml(string strHtml, string strContentStartMarker, string strContentEndMarker) {
			// set defaults for start and end markers if they aren't provided
			if (String.IsNullOrEmpty(strContentStartMarker))
				strContentStartMarker = cstrDefaultContentStartMarker;
			if (String.IsNullOrEmpty(strContentEndMarker))
				strContentEndMarker = cstrDefaultContentEndMarker;

			// try to find the user provided content marker starting at the end
			Match match = Regex.Match(strHtml, strContentEndMarker, RegexOptions.IgnoreCase | RegexOptions.RightToLeft);
			
			// if the user provided end marker didn't work try the default
			if (!match.Success) {
				strContentEndMarker = cstrDefaultContentEndMarker;
				match = Regex.Match(strHtml, strContentEndMarker, RegexOptions.IgnoreCase | RegexOptions.RightToLeft);
			}
			
			// if the user provided or default markers worked then remove everything after and including the end content marker
			if (match.Success)
				strHtml = strHtml.Substring(0, match.Index);

			// try to find the user provided start marker
			match = Regex.Match(strHtml, strContentStartMarker, RegexOptions.IgnoreCase);
			// if the search failed try again with the default end marker
			if (!match.Success) {
				strContentStartMarker = cstrDefaultContentStartMarker;
				match = Regex.Match(strHtml, strContentStartMarker, RegexOptions.IgnoreCase);
			}

			if (match.Success)
				strHtml = strHtml.Substring(match.Index + match.Length);
			return strHtml;
		}

		private static string GetHtmlFromUrl(string strUrl) {
			WebRequest myWebRequest = WebRequest.Create(strUrl);
			WebResponse myWebResponse = myWebRequest.GetResponse();
			Stream ReceiveStream = myWebResponse.GetResponseStream();
			Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
			StreamReader readStream = new StreamReader(ReceiveStream, encode);
			string strResponse = readStream.ReadToEnd();
			return strResponse;
		}

		public void WriteContentFile(string strDownloadToDirectory, string strPreviousUrl, string strNextUrl) {
			string strFilename = this.OsSafeTitle + ".html";
			string strFullHtmlFileName = Path.Combine(strDownloadToDirectory, strFilename);

			string strContent = SurroundWithBasicHtml(this.Content, this.Title, this.Published, this.Url, strPreviousUrl, strNextUrl);

			File.WriteAllText(strFullHtmlFileName, strContent);
		}

		internal XElement RenderAsXHtml() {
			string strClasses = "DailyRss_Entry";
			if (this.Index % 2 == 0)
				strClasses += " DailyRss_OddEntry";
			else
				strClasses += " DailyRss_EvenEntry";

			return new XElement("div", new XAttribute("class", strClasses), 
				new XElement("a", new XAttribute("href", this.OsSafeTitle + ".html"), this.Title),
				new XElement("br"),
				"Published " + this.Published,
				new XElement("br"),
				this.Summary == null ? new XElement("br") : new XElement("div", new XComment(this.Summary)),
				new XElement("br"));
		}
	}
}
